Pull-Down Menus "Bell, book, and candle shall not drive me back, when gold and silver becks me to come on." King John, Shakespeare Introduction We are excited. The pull-down menu features offered in Gold are the most flexible and easy to use in the market place. In this chapter you will learn about the following features of Gold's pull-down menus and status bars: Multiple menu styles are supported, including traditional, chiseled, and a Microsoft Windows look-alike. Pull-down menus support keyboard, mouse and hotkey input. Menus can be nested many levels deep. Separators and grayed items can be easily incorporated. Figure 11.1 A Pull-Down Menu with a Status Bar The Architecture of a Pull-Down Menu A pull-down menu is actually a collection of separate menus bound together. There is always one menu bar in a pull-down menu, even if there is only one item on the bar. The menu bar defines the menu items which appear across the top of the pull-down menu, i.e. the items which are visible even when the menu is inactive. Each item on the menu bar usually has a pop-up menu item defined. Any item in a pop-up menu can point to another pop-up menu thereby providing multiple levels of sub-menus. This scheme of combining a menu bar with several pop-up menus to create a pull-down menu is illustrated in Figure 11.2. Building a Pull-Down menu Enough theory; let's create a menu. Creating a menu is like baking a cake, all you need to do is follow the recipe: Declare the menu variables Construct a menu bar with at least one item. Construct as many pop-up menus as you need. Activate the menu when a menu key is pressed in the application. Call a sub-routine based on the user's menu selection. Creating Menu Variables Every pull-down menu has one menu bar. The menu bar is defined in a variable of type Bar. Additionally, there is one pop-up menu for each menu bar which has related options. Each menu pop-up is define in a variable of type PopUp. The following declaration is extracted from DEMPLL2.PAS which defines a pull-down menu with two items on the menu bar, and two pop-up menus. var MainMenu: Bar; FileMenu: PopUp; OptionsMenu: PopUp; Constructing the Menu Bar A variable of type Bar defines the content of the menu bar. To construct a menu bar, the menu bar must first be initialized with the InitBar procedure, and then items are added to the menu bar with the BarAddItem procedure. These two procedures are defined as follows: InitBar(var P: Bar); Sets a menu bar to the default values. This procedure must be called prior to calling BarAddItem. The procedure is passed the menu bar variable that will be initialized. BarAddItem(var M: Bar; Item:string; ID:integer; HK,AltHK:word; Desc:string; PopUp:pointer); Adds an item to the menu bar. The procedure is passed the menu bar variable, the text of the menu bar entry, the ID that will be returned when the user presses help or selects the menu item, two hotkeys (discussed below), the long description, and finally a pointer to a pop-menu variable (if a pop-up is to be displayed when the menu bar item is selected). Managing Hotkeys There are many different ways that a user can make a choice from a pull-down menu: using the cursor keys to select an item and pressing Enter, using the mouse and double-clicking on the menu item, or pressing a hotkey. Gold supports three different levels of hotkey. As defined above, BarAddItem accepts two hotkey arguments. The first hotkey is the upper-case letter that can be pressed when the menu bar is active and the pop-up menus are not displayed. (This state occurs when you press F10 in the BP7 environment.) If you press the appropriate letter, Gold will jump to the menu item which was defined with that hotkey. The second hotkey passed to BarAddItem defines an alternate key which can be pressed whenever a pop-up is displayed (no matter how nested). The de facto standard is to use an Alt-letter key sequence for the second hotkey. For example, the a menu item define as "File" might have the letter F as the first hot key (70) and Alt-F as the second hotkey (289). To visibly indicate to the user which letter is the hotkey letter, use the "~" character to instruct gold to highlight the letter, e.g. '~F~ile'. This technique works for the menu bar items as well as pop-up items. The third type of hotkey is defined globally and is not directly associated with a specific menu item. For example, in BP7, the File Open menu item is the hotkey F2. To define these additional hotkeys, use the procedure BarAddHK which is defined as follows: BarAddHK(var P: Bar; K:word; HotID:integer); Defines a general hotkey. The procedure is passed the menu bar variable, the hotkey, and the ID which will be returned when the hotkey is pressed. Example Listed below is an extract from DEMPLL2.PAS which initializes a two item menu bar and defines some extra hotkeys: procedure DefineMainMenu; {} begin InitBar(MainMenu); BarAddItem(MainMenu,'~F~ile',100,70,289, 'File management commands (open, new, etc).',@FileMenu); BarAddItem(MainMenu,'~O~ptions',800,79,280, 'Set defaults for compiler, editor, mouse, debugger, etc.',@OptionsMenu); BarAddHK(MainMenu,317,102); {F3 - File Open} BarAddHK(MainMenu,316,103); {F2 - File Save} BarAddHK(MainMenu,301,110); {Alt-X - quit} end; { DefineMainMenu } Notice that the last argument on the File menu constructor is @FileMenu. This informs Gold that the pop-up menu FileMenu should be displayed when the user selects File from the main menu bar. Similarly, the Options menu bar item will call the pop-up OptionsMenu. Constructing the Menu Pop-Ups The process of constructing a pop-up menu is very similar to the process just described for menu bars. A variable of type PopUp defines the content of the pop-up menu. To construct a pop-up menu, the menu must first be initialized with the InitPopUp procedure, and then items are added to the menu with the PopUpAddItem procedure. These two procedure are defined as follows: InitPopup(var M:PopUp); Sets a pop-up menu to the default values. This procedure must be called prior to calling PopUpAddItem. The procedure is passed the popup menu variable that is to be initialized. PopupAddItem(var P:PopUp;Item:string; ID:integer; HK:word; Desc:string; ChildMenu:pointer); Adds an item to the menu. The procedure is passed the pop-up menu variable, the text of the menu item, the ID that will be returned when the user presses help or selects the menu item, a hotkey which can be used to select the item when the pop-up has focus, the long description, and finally a pointer to another popup variable if a pop-up is to be displayed when the menu item is selected -- pass nil if there is no submenu. Listed below is a PopupAddItem statement which defines the "New " item on a file menu: PopUpAddItem(FileMenu,'~N~ew',101,78,'Create a new file in a new Edit window',nil); Defining Separators, Grayed Items and Right Justified Text By adding some reserved characters to the item text, you can exert additional control over the item format. Adding a Separator -- if the menu text is defined as a single minus character "-", Gold will insert a menu separator instead of a normal item. The other parameters are ignored, and can be set to nulls. For example: PopUpAddItem(FileMenu,'-',0,0,'',nil); Graying an Item -- later you will read about the SetActive procedures for making items grayed or selectable. An easy way to make a menu item non-selectable is to insert the exclamation character "!" in the first character of the item text. For example: PopUpAddItem(EditMenu,'!~R~edo',202,82,'Redo the previously undone editor operation',nil); Right Justifying Text -- Traditionally, global hotkeys are displayed right justified in the text of the menu item. To save you the chore of entering the correct number of spaces to right justify the hot key description, simply insert a colon ":" in the item text. All characters to the right of the colon will be right justified. PopUpAddItem(FileMenu,'E~x~it:Alt-X',110,88,'~Exit~ Turbo Pascal',nil); Example The following extract from DEMPLL2.PAS illustrates all the above techniques, and shows how a file menu might be defined: procedure DefineFilePopUp; {} begin InitPopUp(FileMenu); PopUpAddItem(FileMenu,'~N~ew',101,78,'Create a new file in a new Edit window',nil); PopUpAddItem(FileMenu,'~O~pen...:F3',102,79,'Locate and open in an Edit window',nil); PopUpAddItem(FileMenu,'~S~ave:F2',103,83,'Save the file in the active Edit window',nil); PopUpAddItem(FileMenu,'Save ~a~s...',104,65,'Save the current file under a different name directory or drive',nil); PopUpAddItem(FileMenu,'Save a~l~l',105,76,'Save all modified files',nil); PopUpAddItem(FileMenu,'-',0,0,'',nil); {separator} PopUpAddItem(FileMenu,'~C~hange dir...',106,67, 'Choose a new default directory',nil); PopUpAddItem(FileMenu,'~P~rint',107,80,'Print the co ntents of the active Edit window',nil); PopUpAddItem(FileMenu,'P~r~inter setup',108,82, 'Choose printer filter to use for printing',nil); PopUpAddItem(FileMenu,'~D~os shell',109,68, 'Temporily exit to DOS',nil); PopUpAddItem(FileMenu,'E~x~it:Alt-X',110,88,'~Exit~ Turbo Pascal',nil); end; { DefineFilePopUp } Look at the demo files DEMPLL1.PAS through DEMPLL4.PAS to see more examples of menu constructions. Activating a Pull-Down Menu The GOLDDESK unit provides a very powerful and flexible way of managing input to an application using a "desktop" just like the desktop used in BP7. If you have not used a desktop, consider reading the next chapter before deciding to roll-your-own desktop using the primary menu input functions. You will probably find that the desktop meets all of your needs (and then some). Gold, however, does not force you to use the desktop. You can always manage input to the application yourself and pass menu-related keystrokes to the menu for processing. This section describes how. Invoking the Menu The simplest way to pass control to the menu is to call the function ActivatePullmenu. The function is passed the menu bar variable and returns an integer indicating the users selection. For example: Choice := ActivatePullmenu(MainMenu); If the user escaped from the menu without making a selection, a zero is returned. Passing keystrokes to the Menu If a pull-down menu is to form the main task manager for an application, the menu display functions should be placed inside a loop that prompts the user to make a menu selection and then responds to the selection (usually in the form of a case statement). The GOLDMENU unit includes the function IsPullkey which is passed the menu variable along with Gold's standard three entries which define a keystroke, i.e. the key, and the mouse X,Y coordinates. The function returns true if the input should be handled by the pull-down menu. When a true is returned, the application should pass the keystroke to the menu using the PullPushKey function -- this function simply instructs Gold to process the passed keystroke rather than wait for new user input. The following code fragment illustrates this basic technique: with KeyVars do repeat GetInput; if IsPullKey(MainMenu,LastKey,LastX,LastY) then begin Choice := PullPushKey(MainMenu,LastKey,LastX, LastY); case Choice of 101:; 102:; 103:; ........ end; {case} end; until Choice = 999; The following example is an extract of DEMPLL3.PAS which uses the plain ActivateMenu function if the user presses the slash key or F10, and uses PullPushkey if the input is menu related. DefineMenus; DrawBar(MainMenu); MouseShow(true); CursorOff; repeat Choice := 0; GetInput; with KeyVars do begin if (LastKey = 47) {/} or (LastKey = 324) {F10} then Choice := ActivatePullMenu(mainmenu) else if IsPullKey(MainMenu, LastKey, LastX, LastY) then Choice := PullPushKey(MainMenu, LastKey, LastX,LastY); case choice of 101:; {call the appropriate functions} 102:; end; if (Choice <> 0) and (Choice <> 110) then PromptOK(' Gold ','You chose menu ID '+ IntToStr(Choice)); end; until Choice = 110; {the exit choice} MouseShow(false); DisposeMenus; Don't forget that the desktop can take care of all of this input code for you. Further details are provided in the next chapter. Disposing of Menu Memory The menu bar and the menu pop-ups use memory on the heap to store the menu details. When a menu is no longer required, the menu structures must be disposed of by calling DestroyBar and DestroyPopup. The following code is taken from DEMPLL3.PAS: procedure DisposeMenus; {} begin DestroyBar(MainMenu); DestroyPopUp(FileMenu); DestroyPopUp(OptionsMenu); DestroyPopUp(EnvironmentMenu); end; { DisposeMenus } Customizing the Menu's Appearance Like many other Gold offerings, menus use industry standard defaults to make it easy to create a professional and contemporary application. However, various aspects of the menus can be customized to meet individual tastes. Customizing the Menu Style The most dramatic way to change the overall menu appearance is to change the menu style. The menu bar variable is actually a record which contains a field named Style. The Style variable can have any value in the range 1 to 4 as follows: Style Description 1 A BP7-style menu with a single line border. 2 Similar to style 1, but the pop-up menu borders have a chiseled affect. This is the default style. 3 This style takes advantage of Gold's custom characters, and draws window borders along the outermost edge of the pop-ups. 4 Draws a Microsoft Windows look-alike. This style can take advantage of Gold's custom characters. The default menu style is defined by the variable MenuVars.PullStyle. -- its value is assigned to a menu bar when it is initialized. You can experiment with the menu styles by running DEMPLL4.PAS and modifying the Mainmenu.Style value. Customizing Menu Colors You guessed it, you can customize any of the menu colors by calling GoldSetColor. The following elements of TINT affect the menu colors: PullHiHot PullHi PullNormHot PullNorm PullOff PullMsgHot PullMsg PullBorder1 PullBorder2 The following code is an extract from DEMCAL5.PAS which customizes the menu colors: procedure CustomizeColors; {} begin GoldSetColor(PullHiHot,YellowOnBlue); GoldSetColor(PullHi,WhiteOnBlue); GoldSetColor(PullNormHot,YellowOnMagenta); GoldSetColor(PullNorm,WhiteOnMagenta); GoldSetColor(PullOff,LightgrayOnMagenta); GoldSetColor(PullMsgHot,YellowonMagenta); GoldSetColor(PullMsg,WhiteonMagenta); GoldSetColor(PullBorder1,BlackOnmagenta); GoldSetColor(PullBorder2,CyanOnMagenta); end; { CustomizeColors } Customizing Other Menu Components A menu bar (record) variable contains additional fields allowing you to customize the menu bar location, the long prompt location, as well as optional characters used to designate special items and highlight the active item. Controlling the Menu Location It is normal for the menu to occupy the top row of the screen (or the top two rows for the Windows-style menu). However you can position the menu where you want. The menu bar variables TopX and TopY identify the location of the top left of the main menu bar. When a menu bar is initialized, with InitBar, the values of TopX and TopY are set to 1. Controlling the Message Location By default, the long description of the highlighted topic is displayed at the bottom of the display. The message location can be controlled by modifying the following fields of the menu bar record: DescX1, DescX2 and DescY. These control the starting column of the long description, the ending column of the long description, and the row on which the description will be displayed. If you are using a menu style of 4, the menu description should be displayed on the top of the screen. The following extract from DEMPLL4.PAS shows how to adjust the message location for various styles: if MainMenu.Style = 4 then begin ClearLine(1,WhiteOnBlue); ClearLine(2,BlackOnLightgray); MainMenu.DescX1 := 4; MainMenu.DescX2 := 80; MainMenu.DescY := 1; GoldSetColor(PullMsgHot, whiteonblue); GoldSetColor(PullMsg, lightgrayonblue); end Redefining Menu Display Characters Earlier you learned that if the item text was a minus sign a separator would be inserted in the pop-up. You also learned that an item starting with an exclamation would be initially non-selectable, and any text to the right of a colon would be right justified. These special characters are not hard coded, and can be configured to use other characters. To change the characters, simply assign new values to the following three variables: MenuVars.Separator, MenuVars.InActiveChar and MenuVars.TabChar. By assigning different characters to the variables MenuVars.PullLeft and MenuVars.PullRight you can make the active (or highlighted) item standout more clearly. Finally, the character used to indicate that an item has a sub-menu defaults to a right pointing chevron. You can use a different character by assigning a new character value to the MenuVars.PullSubIndicator variable. Adding Menu Help You can instruct Gold to call a custom help procedure to provide menu context sensitive help. All you have to do is create a procedure following some specific rules and call the procedure AssignMenuHelpHook to instruct Gold to call your procedure every time the user requests help while the menu has focus. For a procedure to be eligible as a menu help hook it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to the section Understanding Hooks in chapter 3 for further information. The procedure must be declared with one parameter of type integer. This variable identifies the ID of the active item at the time the user requested help. The following procedure declaration follows these rules: {$F+} procedure MenuHelp(ID:integer); {} begin PromptOK(' Help! ','You asked for help on item ID: ' +IntToStr(ID)); end; { MenuHelp } {$F-} The following procedure is then called to instruct Gold to call your procedure after each input: AssignMenuHelpHook(var M: Bar; Proc:PullHook);; Instructs Gold to call the specified procedure when the user requests help during menu selection. When a help hook is implemented, Gold automatically adds a help string to the long description line, i.e. at coordinates (MenuVars.DescX1, MenuVars.MsgY). The user can click on this area with the mouse, to access the help. International users note that the actual text of the help string is defined in the variable MenuVars.HelpStr and can be modified as necessary. By default the help key is F1; this too can be customized -- just assign an new keystroke value to the variable MenuVars.HelpKey. If, subsequently, you want to remove the help hook, call the procedure RemoveMenuHelpHook. The demo file DEMPLL4.PAS shows how a help hook can be added to a pull-down menu. Utilizing Menu Hind Hooks If you want to write some additional information to the screen based on the active menu item, you should take advantage of the pull-down menu's hind hook. A hind hook is called once when the menu is first displayed, and every time a keystroke or mouse click is processed by the menu. For a procedure to be eligible as a pull-down menu hind hook it must adhere to the following rules: The procedure must be declared as a far procedure at the root level. Refer to the section Understanding Hooks in chapter 3 for further information. The procedure must be declared with one parameter of type integer. This variable identifies the ID of the active item. Just like the help hook described above, the hind hook can be assigned and removed using AssignMenuHindHook and RemoveMenuHindHook, respectively. Changing Menu Settings On-The-Fly Typically, you will create a pull-down menu when the application is started, and destroy the menu when the application is closed. During the session, you can change the look and feel of the menu (without having to destroy and rebuild the menu) by using the following procedures: BarSetActive(var M: Bar; ID:integer; On: boolean); Makes a menu bar item active or inactive. BarChangeItem(var M: Bar; ID:integer; Item,Desc:string; HK,AltHK:word; NewID:integer; PopUp:pointer); Redefines all the components of a menu bar item. BarChangeText(var M: Bar; ID:integer; Item,Desc:string); Redefines the short and long descriptions of a menu bar item. BarDelItem(var M: Bar; ID:integer); Removes an item from a menu bar. BarDelHK(var P: Bar; K:word); Removes a global hot key. PopUpSetActive(var P:PopUp; ID:integer; On: boolean); Makes a pop-up menu item active or inactive. PopUpChangeItem(var P:PopUp; ID:integer; Item,Desc:string;HK:word; NewID:integer; ChildMenu:pointer); Redefines all the components of a pop-up menu item. PopUpChangeText(var P:PopUp; ID:integer; Item,Desc:string); Redefines the short and long descriptions of a pop-up menu item. PopUpDelItem(var P:PopUp; ID:integer); Removes an item from a pop-up menu. Managing Status Bars The good news is that you already know how to create a status bar. A Status bar is actually a menu bar positioned at the bottom of the screen. You'll learn more about status bars in the next chapter. Error Management The pull down menu structures allocate memory on the heap and so have the potential to fail. For example, there may be insufficient free memory to add a menu bar or pop-up item. After calling the BarAddItem and PopupAddItem procedures be sure to call the function LastMenuError to see if the item addition was successful.